package encryption;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Random;

public class Paillier 
{
	static BigInteger two = new BigInteger("2");
	BigInteger n;
	BigInteger g;
	BigInteger y;
	BigInteger m;
	public void setPublicKey(BigInteger n,BigInteger g)
	{
		this.n=n;
		this.g=g;
	}
	public void setPrivateKey(BigInteger y,BigInteger m)
	{
		this.y=y;
		this.m=m;
	}
	
	private BigInteger getMaxTally(int noOfVoters,int maxCandidates,int noOfCandidates)
	{
		BigInteger b=new BigInteger((noOfVoters+1)+"");
		BigInteger mmax=BigInteger.ZERO;
		for(int i=noOfCandidates-1;i>=noOfCandidates-maxCandidates;i--)
		{
			mmax=mmax.add(b.pow(i));
		}
		BigInteger maxTally=mmax.multiply(new BigInteger(noOfVoters+""));
		System.out.println("max Tally "+maxTally);
		return maxTally;
	}
    BigInteger SqRtN(BigInteger N)
    {
      BigInteger G = new BigInteger((N.shiftRight((N.bitLength() + 1) / 2)).toString());
      BigInteger LastG = null;
      BigInteger One = new BigInteger("1");
      while (true)
      {
        LastG = G;
        G = N.divide(G).add(G).shiftRight(1);
        int i = G.compareTo(LastG);
        if (i == 0) return G;
        if (i < 0)
        {
          if (LastG.subtract(G).compareTo(One) == 0)
            if (G.multiply(G).compareTo(N) < 0 && LastG.multiply(LastG).compareTo(N) > 0) return G;
        }
        else
        {
          if (G.subtract(LastG).compareTo(One) == 0)
            if (LastG.multiply(LastG).compareTo(N) < 0 && G.multiply(G).compareTo(N) > 0) return LastG;
        }
      }
      }
    
	public BigInteger[] generateKeys(int noOfVoters,int noOfCandidates,int maxCandidates)
	{
		BigInteger maxTallySq=SqRtN(getMaxTally(noOfVoters,maxCandidates,noOfCandidates));
		System.out.println("sqrt of max tally "+maxTallySq);
		BigInteger p=BigInteger.ONE;
		BigInteger q=BigInteger.ONE;
		int bitLength;
		while(p.equals(q))
		{
			bitLength =(maxTallySq.bitLength())+ ((int)(Math.random()*10));
			p=BigInteger.probablePrime(bitLength, new Random());
			q=BigInteger.probablePrime(bitLength, new Random());
		}
		System.out.println("p "+p+" q"+q);
		n=p.multiply(q);
		
		System.out.println("n "+n);
		
		
		BigInteger pMinus=p.subtract(BigInteger.ONE);
		BigInteger qMinus=q.subtract(BigInteger.ONE);
		
		BigInteger n2=n.multiply(n);
		
		y=((pMinus.multiply(qMinus)).divide(pMinus.gcd(qMinus)));
		
		System.out.println("y "+y);
		int coprimeBitLength=n.bitLength()-((int)(Math.random()*10));
		
		BigInteger alpha=BigInteger.probablePrime(coprimeBitLength, new Random());
		BigInteger beta=BigInteger.probablePrime(coprimeBitLength, new Random());
		
		System.out.println("alpha "+alpha);
		System.out.println("beta "+beta);
		
		g=(((alpha.multiply(n)).add(BigInteger.ONE)).multiply(power(beta,n,n2))).mod(n2);
		
		System.out.println("g "+g);
		m=((((power(g,y,n2)).mod(n2)).subtract(BigInteger.ONE)).divide(n).modInverse(n));
		System.out.println("m "+m);
		
		System.out.println("done with keys");
		return new BigInteger[]{n,g,y,m};
	}
	public BigInteger encrypt(BigInteger m)
	{
		BigInteger c;
		
		int coprimeBitLength=n.bitLength()-((int)(Math.random()*10));
		BigInteger n2=n.multiply(n);
		BigInteger r=BigInteger.probablePrime(coprimeBitLength, new Random());
		c= ((power(g,m,n2)).multiply(power(r,n,n2))).mod(n2);

		return c;
	}
	public BigInteger decrypt(BigInteger c)
	{
		BigInteger me;
		BigInteger n2=n.multiply(n);
		me=(((((power(c,y,n2)).mod(n2)).subtract(BigInteger.ONE)).divide(n)).multiply(m)).mod(n);
		return me;
	}
	public static BigInteger power(BigInteger a , BigInteger b,BigInteger m)
	{
		if(b.equals(BigInteger.ZERO))
			return BigInteger.ONE;
		if((b.mod(two)).equals(BigInteger.ZERO))
		{
			BigInteger t = b.divide(two);
			BigInteger r = power(a,t,m).mod(m);		
			return r.multiply(r);
		}
		BigInteger t = b.divide(two);
		BigInteger r = power(a,t,m).mod(m);
		return (r.multiply(r)).multiply(a);
	}
	public BigInteger createMessage(boolean [] votes,int voters)
	{
		BigInteger b = new BigInteger(""+(voters+1));
		BigInteger sum = new BigInteger("0");
		for(int i = 0 ; i<votes.length ; ++i)
			if(votes[i])
				sum = sum.add(b.pow(i));
		return sum;
	}
	public BigInteger mulVotes(BigInteger [] votes)
	{
		BigInteger mul = new BigInteger("1");
		BigInteger n2 = n.pow(2);
		for(int i = 0 ; i<votes.length ; ++i)
		{
			mul = mul.multiply(votes[i]);
			mul = mul.mod(n2);
		}
		return mul.mod(n2);
	}
	public static ArrayList <String> baseConverter(BigInteger b ,BigInteger base)
	{
		ArrayList <String> result = new ArrayList <String> ();
		while(!b.equals(BigInteger.ZERO))
		{
			result.add(b.mod(base).toString());
			System.out.println(b.mod(base));
			b = b.divide(base);
		}
		return result;
	}
	public static void main(String[]args)
	{
		Paillier p=new Paillier();
		p.setPrivateKey(new BigInteger("341295200"), new BigInteger("141041334"));
		p.setPublicKey(new BigInteger("682643023"), new BigInteger("208474288067458391"));
		boolean [] [] votes = new boolean [] []{new boolean [] {true,false},
				new boolean [] {true,false},
				new boolean [] {true,true},
				new boolean [] {false,true},
				new boolean [] {true,false},
				new boolean [] {true,true},
				new boolean [] {true,false}};
		BigInteger [] msg = new BigInteger [] {new BigInteger("125822225400125229")
		,new BigInteger("409314792492406740")
		,new BigInteger("333116252967719068")
		,new BigInteger("114546746139238576")
		,new BigInteger("19498419983923836")
		,new BigInteger("52564167918439844")
		,new BigInteger("437166720320117473")};
	//	BigInteger [] msg = new BigInteger[votes.length];
	//	for(int i = 0 ;i<votes.length ; ++i)
	//		msg[i] = p.encrypt(p.createMessage(votes[i], 1000));
		BigInteger mul = p.mulVotes(msg);
		BigInteger decrypt = p.decrypt(mul);
		baseConverter(decrypt, new BigInteger("1001"));
		
	}

}
